/**
 *    Copyright 2009-2020 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.builder;

import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.decorators.LruCache;
import org.apache.ibatis.cache.impl.PerpetualCache;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

import java.util.*;

/**
 * Mapper文件解析工具类(映射器构建助手)
 *
 * @author Clinton Begin
 */
public class MapperBuilderAssistant extends BaseBuilder {

    /**
     * 正在解析的MapperXml文件的命名空间(工作空间)
     */
    private String currentNamespace;
    /**
     * 对应的资源文件
     */
    private final String resource;
    /**
     * 当前使用的缓存对象
     */
    private Cache currentCache;
    /**
     * 当前存在未解析的缓存引用
     */
    private boolean unresolvedCacheRef; // issue #676

    public MapperBuilderAssistant(Configuration configuration, String resource) {
        super(configuration);
        ErrorContext.instance().resource(resource);
        this.resource = resource;
    }

    public String getCurrentNamespace() {
        return currentNamespace;
    }

    /**
     * 配置Mybatis Mapper文件解析助手的命名空间
     *
     * @param currentNamespace 命名空间
     */
    public void setCurrentNamespace(String currentNamespace) {
        if (currentNamespace == null) {
            throw new BuilderException("The mapper element requires a namespace attribute to be specified.");
        }

        // 校验命名空间，如果当前已经定义了命名空间的名称，且和传入的命名空间不一致，将会触发异常。
        if (this.currentNamespace != null && !this.currentNamespace.equals(currentNamespace)) {
            throw new BuilderException("Wrong namespace. Expected '"
                    + this.currentNamespace + "' but found '" + currentNamespace + "'.");
        }

        // 赋值
        this.currentNamespace = currentNamespace;
    }

    /**
     * 将指定的标志和当前的命名空间合并
     *
     * @param base        指定的标志
     * @param isReference 是否允许引用其他命名空间
     * @return 合并后的标志
     */
    public String applyCurrentNamespace(String base, boolean isReference) {
        if (base == null) {
            return null;
        }
        if (isReference) {
            // 在允许引用其他命名空间的元素的场景下，如果包含了`.`，则表示已经引用了其他命名空间.
            if (base.contains(".")) {
                return base;
            }
        } else {
            // 在不允许引用其他命名空间的元素的场景下,如果是以当前mapper元素的命名空间+. 开头,则表示使用的是包含了当前
            // 命名空间标志的名称.
            if (base.startsWith(currentNamespace + ".")) {
                return base;
            }
            // 如果不是使用的当前命名空间，同时名称里面还包含了.，那么这是一个不合格的命名方式。
            if (base.contains(".")) {
                throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
            }
        }
        // 合并当前mapper的命名空间和当前元素的唯一标志生成一个全局唯一的新标志
        return currentNamespace + "." + base;
    }

    /**
     * 为当前的映射器配置一个缓存引用
     *
     * @param namespace 被引用缓存的全局唯一标志
     * @return 缓存
     */
    public Cache useCacheRef(String namespace) {
        if (namespace == null) {
            throw new BuilderException("cache-ref element requires a namespace attribute.");
        }
        try {
            unresolvedCacheRef = true;
            // 获取待引用的缓存
            Cache cache = configuration.getCache(namespace);
            if (cache == null) {
                // 引用解析失败
                throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
            }
            currentCache = cache;
            unresolvedCacheRef = false;
            // 返回缓存
            return cache;
        } catch (IllegalArgumentException e) {
            throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
        }
    }

    public Cache useNewCache(Class<? extends Cache> typeClass,
                             Class<? extends Cache> evictionClass,
                             Long flushInterval,
                             Integer size,
                             boolean readWrite,
                             boolean blocking,
                             Properties props) {

        // 给当前的命名空间注册一个新的缓存
        Cache cache = new CacheBuilder(currentNamespace)
                .implementation(valueOrDefault(typeClass, PerpetualCache.class)) /*配置缓存实例，默认PerpetualCache.class*/
                .addDecorator(valueOrDefault(evictionClass, LruCache.class))/*配置缓存回收策略，默认LruCache.class*/
                .clearInterval(flushInterval)/*配置缓存清理时间间隔*/
                .size(size)/*配置缓存能够使用的内存大小*/
                .readWrite(readWrite)/*配置缓存的读写能力*/
                .blocking(blocking)/*配置缓存的阻塞性*/
                .properties(props)/*配置缓存对应的自定义配置*/
                .build();

        // 添加全局缓存引用
        configuration.addCache(cache);
        // 注意看这个位置，如果先添加了缓存引用，又添加局部缓存，那么局部缓存将会覆盖引用的缓存。
        currentCache = cache;
        return cache;
    }

    public ParameterMap addParameterMap(String id, Class<?> parameterClass, List<ParameterMapping> parameterMappings) {
        // 拼接命名空间和唯一ID
        id = applyCurrentNamespace(id, false);
        // 构建一个参数映射表
        ParameterMap parameterMap = new ParameterMap.Builder(configuration, id, parameterClass, parameterMappings).build();
        // 添加参数映射表
        configuration.addParameterMap(parameterMap);

        return parameterMap;
    }

    // 构建ParameterMapping对象
    public ParameterMapping buildParameterMapping(
            Class<?> parameterType,
            String property,
            Class<?> javaType,
            JdbcType jdbcType,
            String resultMap,
            ParameterMode parameterMode,
            Class<? extends TypeHandler<?>> typeHandler,
            Integer numericScale) {

        // 将resultMap标志处理成全局唯一的标志，
        // 如果使用的就是全局引用，直接返回全局引用标记
        // 如果使用的是当前mapper中的resultMap且使用的是内部ID引用，那么将会合并当前mapper的命名空间和resultMap的值。
        resultMap = applyCurrentNamespace(resultMap, true);

        // Class parameterType = parameterMapBuilder.type();
        // 获取参数的java类型
        Class<?> javaTypeClass = resolveParameterJavaType(parameterType, property, javaType, jdbcType);
        // 获取类型转换处理器实例
        TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);

        return new ParameterMapping.Builder(
                configuration/*Mybatis配置*/
                , property/*参数名称*/
                , javaTypeClass /*java类型*/
        )
                .jdbcType(jdbcType)/*参数的jdbc类型*/
                .resultMapId(resultMap)/*引用的resultMap的全局唯一标志*/
                .mode(parameterMode)/*参数类型*/
                .numericScale(numericScale)/*小数保留位数*/
                .typeHandler(typeHandlerInstance)/*类型转换处理器*/
                .build();
    }

    /**
     * 添加(注册)一个ResultMap集合
     *
     * @param id             ResultMap唯一标志
     * @param type           返回类型
     * @param extend         继承的ResultMap
     * @param discriminator  鉴别器
     * @param resultMappings 现有的ResultMapping集合
     * @param autoMapping    是否自动处理类型转换
     * @return ResultMap
     */
    public ResultMap addResultMap(
            String id,
            Class<?> type,
            String extend,
            Discriminator discriminator,
            List<ResultMapping> resultMappings,
            Boolean autoMapping) {
        // 获取命名空间标志
        id = applyCurrentNamespace(id, false);
        // 继承的命名空间
        extend = applyCurrentNamespace(extend, true);

        if (extend != null) {
            if (!configuration.hasResultMap(extend)) {
                // 不存在引用(继承)的ResultMap，标记为incomplete,待第二次处理
                throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
            }
            // 获取被引入(继承)的ResultMaps
            ResultMap resultMap = configuration.getResultMap(extend);
            // 获取引入的resultMap的所有子节点配置
            List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
            // 本地覆盖继承
            extendedResultMappings.removeAll(resultMappings);
            // Remove parent constructor if this resultMap declares a constructor.
            // 当前resultMap是否声明了构造函数
            boolean declaresConstructor = false;
            for (ResultMapping resultMapping : resultMappings) {
                if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                    // 当前resultMap声明了构造函数
                    declaresConstructor = true;
                    break;
                }
            }
            if (declaresConstructor) {
                // 如果已经声明了构造函数，准备移除父resultMap的构造函数
                Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
                while (extendedResultMappingsIter.hasNext()) {
                    if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                        // 移除被继承的resultMap的构造函数
                        extendedResultMappingsIter.remove();
                    }
                }
            }
            //合并自身的resultMap以及继承的resultMap的内容,获得最终的resultMap,这也意味着在启动时就创建了完整的resultMap，
            // 这样在运行时就不需要去检查继承的映射和构造器，有利于性能提升。
            resultMappings.addAll(extendedResultMappings);
        }

        // 构造ResultMap
        ResultMap resultMap = new ResultMap
                .Builder(
                configuration
                , id
                , type
                , resultMappings
                , autoMapping
        )
                .discriminator(discriminator)
                .build();

        // 注册resultMap
        configuration.addResultMap(resultMap);
        return resultMap;
    }

    /**
     * 构造Discriminator对象
     *
     * @param resultType       返回类型
     * @param column           列名称
     * @param javaType         java类型
     * @param jdbcType         jdbc类型
     * @param typeHandler      类型转换处理器
     * @param discriminatorMap 鉴别器指定字段的值和resultMap的关联关系
     * @return Discriminator对象
     */
    public Discriminator buildDiscriminator(
            Class<?> resultType,
            String column,
            Class<?> javaType,
            JdbcType jdbcType,
            Class<? extends TypeHandler<?>> typeHandler,
            Map<String, String> discriminatorMap) {
        // 构建resultMap
        ResultMapping resultMapping = buildResultMapping(
                resultType,
                null,
                column,
                javaType,
                jdbcType,
                null,
                null,
                null,
                null,
                typeHandler,
                new ArrayList<ResultFlag>(),
                null,
                null,
                false);

        Map<String, String> namespaceDiscriminatorMap = new HashMap<>();

        // 循环处理所有的case元素的定义
        for (Map.Entry<String, String> e : discriminatorMap.entrySet()) {
            String resultMap = e.getValue();
            // 拼接命名空间
            resultMap = applyCurrentNamespace(resultMap, true);

            // 更新鉴别器和resultMap的关系
            namespaceDiscriminatorMap.put(e.getKey(), resultMap);
        }

        // 构建鉴别器
        return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();
    }

    /**
     * 创建MappedStatement对象并将其注册到Configuration对象的声明语句映射表中
     *
     * @param id             当前声明语句的唯一标志
     * @param sqlSource      用于维护Sql内容的SqlSource对象
     * @param statementType  JDBC Statement的类型
     * @param sqlCommandType 对应SQL的类型
     * @param fetchSize      获取数据库内容大小的限制
     * @param timeout        驱动等待数据库返回内容的超时时间
     * @param parameterMap   请求入参映射表
     * @param parameterType  请求入参对象
     * @param resultMap      返回参数映射表
     * @param resultType     返回参数对象
     * @param resultSetType  JDBC ResultSet对象的类型
     * @param flushCache     是否刷新缓存
     * @param useCache       是否使用缓存
     * @param resultOrdered  是否对返回结果进行排序操作
     * @param keyGenerator   使用的主键生成器
     * @param keyProperty    自动映射值的属性名称列表
     * @param keyColumn      自动映射值的数据库列名称列表
     * @param databaseId     对应的数据库唯一标志
     * @param lang           解析`CRUD`元素的语句驱动器
     * @param resultSets     对于多返回结果集的名称配置列表
     * @return MappedStatement对象
     */
    public MappedStatement addMappedStatement(
            String id,
            SqlSource sqlSource,
            StatementType statementType,
            SqlCommandType sqlCommandType,
            Integer fetchSize,
            Integer timeout,
            String parameterMap,
            Class<?> parameterType,
            String resultMap,
            Class<?> resultType,
            ResultSetType resultSetType,
            boolean flushCache,
            boolean useCache,
            boolean resultOrdered,
            KeyGenerator keyGenerator,
            String keyProperty,
            String keyColumn,
            String databaseId,
            LanguageDriver lang,
            String resultSets) {

        if (unresolvedCacheRef) {
            // 未完成缓存的引用工作
            throw new IncompleteElementException("Cache-ref not yet resolved");
        }
        // 合并命名空间，将其转换为全局唯一的标志
        id = applyCurrentNamespace(id, false);

        // 判断当前语句是否为查询语句
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

        // 生成映射声明
        MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
                .resource(resource)
                .fetchSize(fetchSize)
                .timeout(timeout)
                .statementType(statementType)
                .keyGenerator(keyGenerator)
                .keyProperty(keyProperty)
                .keyColumn(keyColumn)
                .databaseId(databaseId)
                .lang(lang)
                .resultOrdered(resultOrdered)
                .resultSets(resultSets)
                .resultMaps(getStatementResultMaps(resultMap, resultType, id))
                .resultSetType(resultSetType)
                .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
                .useCache(valueOrDefault(useCache, isSelect))
                .cache(currentCache);

        // 生成参数映射声明
        ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);

        if (statementParameterMap != null) {
            // 加入参数映射声明
            statementBuilder.parameterMap(statementParameterMap);
        }

        /*
         * 生成映射语句
         */
        MappedStatement statement = statementBuilder.build();
        /*
         * 添加映射
         */
        configuration.addMappedStatement(statement);

        return statement;
    }

    private <T> T valueOrDefault(T value, T defaultValue) {
        return value == null ? defaultValue : value;
    }

    /**
     * 生成参数映射
     *
     * @param parameterMapName   参数映射名称
     * @param parameterTypeClass 参数映射类型
     * @param statementId        声明语句的唯一标志
     */
    private ParameterMap getStatementParameterMap(
            String parameterMapName,
            Class<?> parameterTypeClass,
            String statementId) {
        // 合并命名空间
        parameterMapName = applyCurrentNamespace(parameterMapName, true);
        ParameterMap parameterMap = null;
        if (parameterMapName != null) {
            try {
                parameterMap = configuration.getParameterMap(parameterMapName);
            } catch (IllegalArgumentException e) {
                throw new IncompleteElementException("Could not find parameter map " + parameterMapName, e);
            }
        } else if (parameterTypeClass != null) {
            // 根据参数类型生成参数映射
            List<ParameterMapping> parameterMappings = new ArrayList<>();
            parameterMap =
                    new ParameterMap.Builder(
                            configuration,
                            statementId + "-Inline",
                            parameterTypeClass,
                            parameterMappings
                    ).build();
        }
        return parameterMap;
    }

    private List<ResultMap> getStatementResultMaps(
            String resultMap,
            Class<?> resultType,
            String statementId) {
        resultMap = applyCurrentNamespace(resultMap, true);

        List<ResultMap> resultMaps = new ArrayList<>();
        if (resultMap != null) {
            String[] resultMapNames = resultMap.split(",");
            for (String resultMapName : resultMapNames) {
                try {
                    resultMaps.add(configuration.getResultMap(resultMapName.trim()));
                } catch (IllegalArgumentException e) {
                    throw new IncompleteElementException("Could not find result map " + resultMapName, e);
                }
            }
        } else if (resultType != null) {
            ResultMap inlineResultMap = new ResultMap.Builder(
                    configuration,
                    statementId + "-Inline",
                    resultType,
                    new ArrayList<ResultMapping>(),
                    null).build();
            resultMaps.add(inlineResultMap);
        }
        return resultMaps;
    }

    /**
     * 构建ResultMapping实体
     *
     * @param resultType      返回类型
     * @param property        属性名称
     * @param column          字段名称
     * @param javaType        java类型
     * @param jdbcType        jdbc类型
     * @param nestedSelect    嵌套的查询语句
     * @param nestedResultMap 嵌套的resultMap
     * @param notNullColumn   非空字段
     * @param columnPrefix    列前缀
     * @param typeHandler     类型处理器
     * @param flags           属性标记
     * @param resultSet       多结果集定义
     * @param foreignColumn   父数据列名称集合
     * @param lazy            懒加载标记
     */
    public ResultMapping buildResultMapping(
            Class<?> resultType,
            String property,
            String column,
            Class<?> javaType,
            JdbcType jdbcType,
            String nestedSelect,
            String nestedResultMap,
            String notNullColumn,
            String columnPrefix,
            Class<? extends TypeHandler<?>> typeHandler,
            List<ResultFlag> flags,
            String resultSet,
            String foreignColumn,
            boolean lazy) {
        // 推断返回的java类型
        Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);

        // 解析类型处理器
        TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);

        // 解析混合列，在Mybatis中对于嵌套查询，我们可以在定义column的时候，使用column= "{prop1=col1,prop2=col2}"
        // 这样的语法来配置多个列名传入到嵌套查询语句中的名称。其中prop1表示嵌套查询中的参数名称，col1表示主查询中列名称。
        List<ResultMapping> composites = parseCompositeColumnName(column);

        // 构建ResultMapping
        return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
                .jdbcType(jdbcType)
                .nestedQueryId(applyCurrentNamespace(nestedSelect, true))/*处理嵌套查询的ID*/
                .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))/*处理嵌套ResultMap的Id*/
                .resultSet(resultSet)
                .typeHandler(typeHandlerInstance)
                .flags(flags == null ? new ArrayList<ResultFlag>() : flags)
                .composites(composites) /*混合列*/
                .notNullColumns(parseMultipleColumnNames(notNullColumn))
                .columnPrefix(columnPrefix)
                .foreignColumn(foreignColumn)
                .lazy(lazy)
                .build();
    }


    private Set<String> parseMultipleColumnNames(String columnName) {
        Set<String> columns = new HashSet<>();
        if (columnName != null) {
            if (columnName.indexOf(',') > -1) {
                // 指定的columnsName中包含了,
                StringTokenizer parser = new StringTokenizer(columnName, "{}, ", false);
                while (parser.hasMoreTokens()) {
                    // 以{开始,以}结束，以,分隔获取多个column字段
                    String column = parser.nextToken();
                    columns.add(column);
                }
            } else {
                columns.add(columnName);
            }
        }
        return columns;
    }

    /**
     * 解析混合列
     * 示例:
     * 【column="{RoleId=id,createTime=registryTime}"】
     * 解析成
     * {
     * property="RoleId",column="id",
     * }
     * {
     * property="createTime",column="registryTime",
     * }
     *
     * @param columnName 列名称
     */
    private List<ResultMapping> parseCompositeColumnName(String columnName) {
        List<ResultMapping> composites = new ArrayList<>();
        if (columnName != null && (columnName.indexOf('=') > -1 || columnName.indexOf(',') > -1)) {
            // 以 【{}=,】 作为分隔符处理内容
            StringTokenizer parser = new StringTokenizer(columnName, "{}=, ", false);
            while (parser.hasMoreTokens()) {
                // 获取属性名称
                String property = parser.nextToken();
                // 获取列名称
                String column = parser.nextToken();

                ResultMapping complexResultMapping = new ResultMapping.Builder(
                        configuration /*Mybatis配置*/
                        , property/*属性名称*/
                        , column/*列名称*/
                        , configuration.getTypeHandlerRegistry().getUnknownTypeHandler()/*Mybatis默认的未知类型的转换处理器*/
                ).build();

                composites.add(complexResultMapping);
            }
        }
        return composites;
    }

    /**
     * 解析返回的java类型
     *
     * @param resultType 返回类型
     * @param property   字段名称
     * @param javaType   java类型
     */
    private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {

        if (javaType == null && property != null) {
            // 没有javaType根据类的元数据集合获取javaType
            try {
                MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
                javaType = metaResultType.getSetterType(property);
            } catch (Exception e) {
                //ignore, following null check statement will deal with the situation
            }
        }
        if (javaType == null) {
            javaType = Object.class;
        }
        return javaType;
    }

    // 解析参数的java类型
    private Class<?> resolveParameterJavaType(Class<?> resultType, String property, Class<?> javaType, JdbcType jdbcType) {
        if (javaType == null) {
            if (JdbcType.CURSOR.equals(jdbcType)) {
                // 游标对应ResultSet
                javaType = java.sql.ResultSet.class;
            } else if (Map.class.isAssignableFrom(resultType)) {
                // Map对应Object
                javaType = Object.class;
            } else {
                // 通过反射获取java类型
                MetaClass metaResultType = MetaClass.forClass(resultType, configuration.getReflectorFactory());
                javaType = metaResultType.getGetterType(property);
            }
        }
        if (javaType == null) {
            // 默认为Object
            javaType = Object.class;
        }
        return javaType;
    }

    /**
     * Backward compatibility signature
     */
    public ResultMapping buildResultMapping(
            Class<?> resultType,
            String property,
            String column,
            Class<?> javaType,
            JdbcType jdbcType,
            String nestedSelect,
            String nestedResultMap,
            String notNullColumn,
            String columnPrefix,
            Class<? extends TypeHandler<?>> typeHandler,
            List<ResultFlag> flags) {
        return buildResultMapping(
                resultType, property, column, javaType, jdbcType, nestedSelect,
                nestedResultMap, notNullColumn, columnPrefix, typeHandler, flags, null, null, configuration.isLazyLoadingEnabled());
    }

    public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass) {
        if (langClass != null) {
            // 注册语言解析器
            configuration.getLanguageRegistry().register(langClass);
        } else {
            // 使用默认的语言解析器
            langClass = configuration.getLanguageRegistry().getDefaultDriverClass();
        }
        // 获取语言解析器
        return configuration.getLanguageRegistry().getDriver(langClass);
    }

    /**
     * Backward compatibility signature
     */
    public MappedStatement addMappedStatement(
            String id,
            SqlSource sqlSource,
            StatementType statementType,
            SqlCommandType sqlCommandType,
            Integer fetchSize,
            Integer timeout,
            String parameterMap,
            Class<?> parameterType,
            String resultMap,
            Class<?> resultType,
            ResultSetType resultSetType,
            boolean flushCache,
            boolean useCache,
            boolean resultOrdered,
            KeyGenerator keyGenerator,
            String keyProperty,
            String keyColumn,
            String databaseId,
            LanguageDriver lang) {
        return addMappedStatement(
                id, sqlSource, statementType, sqlCommandType, fetchSize, timeout,
                parameterMap, parameterType, resultMap, resultType, resultSetType,
                flushCache, useCache, resultOrdered, keyGenerator, keyProperty,
                keyColumn, databaseId, lang, null);
    }

}
